﻿using System;
using System.Activities;
using System.Activities.Presentation.PropertyEditing;
using System.Activities.Statements;
using System.Activities.Validation;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Markup;
using WFExtended.Activities.Designers;
using WFExtended.Framework.Constraints;
using WFExtended.Framework.Presentation;
using WFExtended.Framework.ServiceModel;

namespace WFExtended.Activities
{
    /// <summary>
    /// Base WCF Proxy Activity
    /// </summary>
    /// <typeparam name="T">Type of the Service Contract</typeparam>
    public abstract class AWcfProxyActivity<T> : NativeActivity, IWcfProxyActivity
          where T : class
    {

        /// <summary>
        /// Initializes a new instance of the <see cref="AWcfProxyActivity{T}"/> class.
        /// </summary>
        public AWcfProxyActivity()
            : this("proxy", GetDefaultConstraints())
        {

        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AWcfProxyActivity{T}"/> class.
        /// </summary>
        /// <param name="proxyName">Name of the proxy.</param>
        public AWcfProxyActivity(string proxyName = "proxy", params Constraint[] constraints)
        {
            Body = new ActivityAction<WcfProxy<T>>()
            {
                Argument = new DelegateInArgument<WcfProxy<T>>(proxyName)
            };

            Proxy = new InArgument<WcfProxy<T>>();
            EndPointBehaviors = new InArgument<List<IEndpointBehavior>>();
            Headers = new InArgument<List<AddressHeader>>();
            EnablePersistence = true;
            _noPersistHandle = new Variable<NoPersistHandle>();

            foreach (Constraint constraint in constraints)
                this.Constraints.Add(constraint);
        }

        private static Constraint[] GetDefaultConstraints()
        {
            return new Constraint[]
            {
                 new ContainConstraint<AWcfProxyActivity<T>>(true, "Invoker", typeof(WcfOperationInvoker<>), typeof(WcfOperationInvoker)).GetConstraint(),
                 new ServiceConstraint<AWcfProxyActivity<T>>(false, "Contract", typeof(T)).GetConstraint()
            };
        }

        private Variable<NoPersistHandle> _noPersistHandle;

        #region Aciviity Properties

        /// <summary>
        /// Get or Set the type of the Channel
        /// </summary>
        [Browsable(true)]
        [DefaultValue(ChannelType.Standard)]
        [Category("Proxy")]
        [Description("Get or Set the type of the Channel")]
        public virtual ChannelType ChannelType { get; set; }

        /// <summary>
        /// Get or Set if the persistence enabled
        /// </summary>
        [Browsable(true)]
        [Category("Proxy")]
        [Description("Get or Set if the persistence enabled")]
        public virtual bool EnablePersistence { get; set; }

        /// <summary>
        /// Get or Set the list of EndPoints Behavior that will be activated
        /// </summary>
        [Browsable(true)]
        [Category("Endpoint")]
        [Description("Get or Set the list of EndPoints Behavior that will be activated")]
        public virtual InArgument<List<IEndpointBehavior>> EndPointBehaviors { get; set; }

        /// <summary>
        /// Get or Set the list of Headers thant will be send for each request
        /// </summary>
        [Browsable(true)]
        [Category("Endpoint")]
        [Description("Get or Set the list of Headers thant will be send for each request")]
        public virtual InArgument<List<AddressHeader>> Headers { get; set; }


        /// <summary>
        /// Get or Set the number of retry for a faulted call 
        /// </summary>
        [Browsable(true)]
        [Category("Exception")]
        [DefaultValue(0)]
        [Description("Get or Set the number of retry for a faulted call ")]
        public virtual InArgument<int> RetryCount { get; set; }

        /// <summary>
        /// Get or Set the delay between each retry
        /// </summary>
        [Browsable(true)]
        [Category("Exception")]
        [Description("Get or Set the delay between each retry")]
        public virtual InArgument<TimeSpan> RetryDelay { get; set; }

        /// <summary>
        /// Get or Set the Proxy
        /// </summary>
        [Browsable(false)]
        public InArgument<WcfProxy<T>> Proxy { get; set; }

        #endregion

        /// <summary>
        /// Get Or Set the Body of the activity
        /// </summary>
        [Browsable(false)]
        [DefaultValue(null)]
        public ActivityAction<WcfProxy<T>> Body { get; set; }

        /// <summary>
        /// Get the Service Endpoint
        /// </summary>
        /// <param name="context">The execution context in which the activity executes</param>
        /// <returns>Return the Service Endpoint</returns>
        protected abstract ServiceEndpoint GetServiceEndpoint(NativeActivityContext context);

        /// <summary>
        /// Get the Proxy Service Type
        /// </summary>
        public Type ProxyType
        {
            get { return typeof(T); }
        }

        /// <summary>
        /// Creates and validates a description of the activity’s arguments, variables, child activities, and activity delegates.
        /// </summary>
        /// <param name="metadata">The activity’s metadata that encapsulates the activity’s arguments, variables, child activities, and activity delegates.</param>
        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            Collection<RuntimeArgument> arguments = new Collection<RuntimeArgument>();

            RuntimeArgument valuesArgument = new RuntimeArgument("Proxy", typeof(WcfProxy<T>), ArgumentDirection.In);
            metadata.Bind(Proxy, valuesArgument);
            arguments.Add(valuesArgument);

            RuntimeArgument endPointsArgument = new RuntimeArgument("EndPointBehaviors", typeof(List<IEndpointBehavior>), ArgumentDirection.In);
            metadata.Bind(EndPointBehaviors, endPointsArgument);
            arguments.Add(endPointsArgument);

            RuntimeArgument headersArgument = new RuntimeArgument("Headers", typeof(List<AddressHeader>), ArgumentDirection.In);
            metadata.Bind(Headers, headersArgument);
            arguments.Add(headersArgument);

            metadata.SetArgumentsCollection(arguments);

            if (!EnablePersistence)
            {
                NotContainConstraint<AWcfProxyActivity<T>> noPersistConstraint =
                    new NotContainConstraint<AWcfProxyActivity<T>>(false, "Persistence", typeof(Persist));
                this.Constraints.Add(noPersistConstraint.GetConstraint());
            }

            metadata.AddDelegate(Body);
            metadata.AddImplementationVariable(_noPersistHandle);

            base.CacheMetadata(metadata);
          
        }

        /// <summary>
        /// Runs the activity’s execution logic.
        /// </summary>
        /// <param name="context">The execution context in which the activity executes</param>
        protected override void Execute(NativeActivityContext context)
        {
            if (!EnablePersistence)
            {
                NoPersistHandle handle = _noPersistHandle.Get(context);
                handle.Enter(context);
            }

            WcfProxy<T> proxy = new WcfProxy<T>(RetryCount.Get(context), RetryDelay.Get(context));
            proxy.ChannelType = ChannelType;
            proxy.EndPoint = GetServiceEndpoint(context);

            var endPoints = EndPointBehaviors.Get(context);
            if(endPoints != null)
                foreach (var endPointBehavior in endPoints)
                    proxy.EndPoint.EndpointBehaviors.Add(endPointBehavior);

            Proxy.Set(context, proxy);

            context.ScheduleAction<WcfProxy<T>>(Body, Proxy.Get(context), OnBodyComplete, OnBodyFaulted);
        }


        private void OnBodyComplete(NativeActivityContext context, ActivityInstance completedInstance)
        {
            DisposeProxy(context);

            if (!EnablePersistence)
            {
                NoPersistHandle handle = _noPersistHandle.Get(context);
                handle.Exit(context);
            }
        }

        private void OnBodyFaulted(NativeActivityFaultContext faultContext, Exception propagatedException, ActivityInstance propagatedFrom)
        {
            DisposeProxy(faultContext);

            if (!EnablePersistence)
            {
                NoPersistHandle handle = _noPersistHandle.Get(faultContext);
                handle.Exit(faultContext);
            }
        }

        private void DisposeProxy(NativeActivityContext context)
        {
            WcfProxy<T> proxy = Proxy.Get(context);
            proxy.Dispose();
            proxy = null;
        }


    }
}
